﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace HistGramEqualize
{
    public unsafe partial class FrmMain : Form
    {

        //'*****************************************************************************************
        //'**    开发日期 ：  2013-7-21
        //'**    作    者 ：  laviewpbt
        //'**    联系方式：   33184777
        //'**    修改日期 ：  2013-7-
        //'**    版    本 ：  Version 1.0.0
        //'**    转载请不要删除以上信息
        //'****************************************************************************************

        [DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = true)]
        private static extern void CopyMemory(byte* Dest, byte* src, int Length);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        private static extern IntPtr GlobalAlloc(uint Flags, int Length);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        private extern static IntPtr GlobalFree(IntPtr Handle);

        private const uint GMEM_FIXED = 0X0;
        private const uint GMEM_ZEROINIT = 0X40;
        private const uint GPTR = (GMEM_FIXED | GMEM_ZEROINIT);

        public FrmMain()
        {
            InitializeComponent();
        }

        private void CmdLoad_Click(object sender, EventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();
            openFileDialog.Filter = "Bitmap files (*.bmp)|*.bmp|Jpeg files (*.jpg)|*.jpg|Png files (*.png)|*.png|All valid files (*.bmp/*.jpg/*.png)|*.bmp;*.jpg;*.png";
            openFileDialog.FilterIndex = 4;
            openFileDialog.RestoreDirectory = true;
            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                Pic.Image = (Bitmap)Bitmap.FromFile(openFileDialog.FileName);
            }
        }

     
        private void CmdConvertToGrayMode_Click(object sender, EventArgs e)
        {
            if (IsGrayBitmap((Bitmap)Pic.Image) == false)
            {
                Bitmap GrayBmp = ConvertToGrayModeBitmap((Bitmap)Pic.Image);
                Pic.Image.Dispose();
                Pic.Image = GrayBmp;
            }
        }

        private void CMDSIS_Click(object sender, EventArgs e)
        {
            if (IsGrayBitmap((Bitmap)Pic.Image) == false)
                MessageBox.Show("无法处理非灰度图像.");
            else 
            {
                Stopwatch Sw = new Stopwatch();
                Sw.Start();
                byte  Value = GetSimpleStatisticsThreshold((Bitmap)Pic.Image);
                Threshold((Bitmap)Pic.Image,Value);
                Sw.Stop();
                LblInfo.Text = "阈值:" + Value.ToString() + " 处理用时: " + Sw.ElapsedMilliseconds.ToString() + "ms"; 
                Pic.Invalidate();
            }
        }

        private bool IsGrayBitmap(Bitmap Bmp)
        {
            bool IsGray;
            if (Bmp.PixelFormat == PixelFormat.Format8bppIndexed)           // .net中灰度首先必然是索引图像
            {
                IsGray = true;
                if (Bmp.Palette.Entries.Length != 256)                      // 这个要求其实在PS中是不存在的
                    IsGray = false;
                else
                {
                    for (int X = 0; X < Bmp.Palette.Entries.Length; X++)    // 看看调色板的每一个分两值是不是都相等，且必须还要等于其在调色板中出现的顺序
                    {
                        if (Bmp.Palette.Entries[X].R != X || Bmp.Palette.Entries[X].G != X || Bmp.Palette.Entries[X].B != X)
                        {
                            IsGray = false;
                            break;
                        }
                    }
                }
            }
            else
            {
                IsGray = false;
            }
            return IsGray;
        }

        private Bitmap ConvertToGrayModeBitmap(Bitmap Bmp)
        {
            int X, Y, SrcStride, DestStride, Width, Height;
            byte* SrcData, DestData;
            BitmapData BmpData = Bmp.LockBits(new Rectangle(0, 0, Bmp.Width, Bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
            Bitmap GrayBmp = new Bitmap(Bmp.Width, Bmp.Height, PixelFormat.Format8bppIndexed);
            ColorPalette Pal = GrayBmp.Palette;
            for (Y = 0; Y < Pal.Entries.Length; Y++) Pal.Entries[Y] = Color.FromArgb(255, Y, Y, Y);            // 设置灰度图像的调色板
            GrayBmp.Palette = Pal;

            //  LockBits 在第一个参数和图像一样大，以及读取格式和原始一样的情况下，调用函数的时间为0，且每次调用后BitmapData的Scan0都相同，而在
            // 其他的大部分情况下同样参数调用该函数返回的Scan0都不同，这就说明在在程序内部，GDI+为在创建图像时还是分配了和对应位图一样大小内存空间，
            // 这样我们就可以再加载时调用一次该函数，并记住Scan0的值，然后直接用指针操作这一片区域，就相当于操作了图像。而不用每次都LOCK和UNLOCK了
            // 从这个层次上说，该函数和GetDibits类似。

            BitmapData GrayBmpData = GrayBmp.LockBits(new Rectangle(0, 0, GrayBmp.Width, GrayBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed);
            Width = BmpData.Width; Height = BmpData.Height;
            SrcStride = BmpData.Stride; DestStride = GrayBmpData.Stride;           //  这个值并不一定就等于width*height*色深/8
            for (Y = 0; Y < Height; Y++)
            {
                SrcData = (byte*)BmpData.Scan0 + Y * SrcStride;                  // 必须在某个地方开启unsafe功能，其实C#中的unsafe很safe，搞的好吓人。
                DestData = (byte*)GrayBmpData.Scan0 + Y * DestStride;
                for (X = 0; X < Width; X++)
                {
                    *DestData = (byte)((*SrcData * 7472 + *(SrcData + 1) * 38469 + *(SrcData + 2) * 19595) >> 16);        //这里可以有不同的算法
                    SrcData += 3;
                    DestData++;
                }
            }
            Bmp.UnlockBits(BmpData);
            GrayBmp.UnlockBits(GrayBmpData);
            return GrayBmp;
        }

        private byte GetSimpleStatisticsThreshold(Bitmap GrayBmp)
        {
            int Width, Height, Stride, X, Y;
            int CloneStride, Ex, Ey;
            int Weight = 0;
            long SumWeight = 0;                 //  对于大图像这个数字会溢出，所以用long类型的变量
            byte* Pointer, Scan0, CloneData;

            BitmapData GrayBmpData = GrayBmp.LockBits(new Rectangle(0, 0, GrayBmp.Width, GrayBmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);

            Width = GrayBmp.Width; Height = GrayBmp.Height; Stride = GrayBmpData.Stride; CloneStride = Width + 2; Scan0 = (byte*)GrayBmpData.Scan0;
            CloneData = (byte*)GlobalAlloc(GPTR, CloneStride * (Height * 2));

            for (Y = 0; Y < Height; Y++)
            {
                *(CloneData + (Y + 1) * CloneStride) = *(Scan0 + Y * Stride);                           //  填充左侧第一列像素(不包括第一个和最后一个点)
                CopyMemory(CloneData + CloneStride * (Y + 1) + 1, Scan0 + Y * Stride, Width);
                *(CloneData + (Y + 1) * CloneStride + Width + 1) = *(Scan0 + Y * Stride + Width - 1);   //  填充最右侧那一列的数据
            }
            CopyMemory(CloneData, CloneData + CloneStride, CloneStride);                                //   第一行
            CopyMemory(CloneData + (Height + 1) * CloneStride, CloneData + Height * CloneStride, CloneStride);    //   最后一行

            for (Y = 0; Y < Height; Y++)
            {
                Pointer = CloneData + (Y + 1) * CloneStride + 1;
                for (X = 0; X < Width; X++)
                {
                    Ex = *(Pointer - 1) - *(Pointer + 1);
                    Ex = Math.Abs(Ex);
                    if (Ex < 0) Ex = -Ex;
                    Ey = *(Pointer - CloneStride) - *(Pointer + CloneStride);
                    if (Ey < 0) Ey = -Ey;
                    if (Ex > Ey)
                    {
                        Weight += Ex;
                        SumWeight += *Pointer * Ex;
                    }
                    else
                    {
                        Weight += Ey;
                        SumWeight += *Pointer * Ey;
                    }
                    Pointer++;
                }
            }
            GlobalFree((IntPtr)CloneData);
            GrayBmp.UnlockBits(GrayBmpData);
            if (Weight == 0) return *(Scan0);  //  说明所有的颜色值都相同
            return (byte)(SumWeight / Weight);
        }

        private void Threshold(Bitmap GrayBmp, byte Threshold)
        {
            BitmapData GrayBmpData = GrayBmp.LockBits(new Rectangle(0, 0, GrayBmp.Width, GrayBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed);
            byte* Pointer = (byte*)GrayBmpData.Scan0;
            for (int Y = 0; Y < GrayBmpData.Height * GrayBmpData.Stride; Y++) Pointer[Y] = (byte)((Pointer[Y] > Threshold) ? 255 : 0);
            GrayBmp.UnlockBits(GrayBmpData);
        }
    }
}
